package cn.iocoder.yudao.module.engine.websocket.engine;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.handlermapping.NettyWsChannelContext;
import cn.iocoder.yudao.framework.handlermapping.NettyWsHandlerComponent;
import cn.iocoder.yudao.framework.handlermapping.NettyWsMapping;
import cn.iocoder.yudao.framework.handlermapping.WsSessionContext;
import cn.iocoder.yudao.framework.message.ClientRequest;
import cn.iocoder.yudao.framework.server.websocket.event.WebSocketAuthSuccessEvent;
import cn.iocoder.yudao.framework.server.websocket.event.WebSocketDisconnectEvent;
import cn.iocoder.yudao.framework.server.websocket.security.WebSocketUser;
import cn.iocoder.yudao.module.engine.constant.EngineConstant;
import cn.iocoder.yudao.module.engine.dal.redis.engine.EngineCacheRedisDAO;
import cn.iocoder.yudao.module.engine.enums.EnumEngineAipError;
import cn.iocoder.yudao.module.engine.enums.EnumInteractionType;
import cn.iocoder.yudao.module.engine.model.config.EngineConfig;
import cn.iocoder.yudao.module.engine.model.context.EngineSessionContext;
import cn.iocoder.yudao.module.engine.model.context.EngineSynStatusCacheContext;
import cn.iocoder.yudao.module.engine.model.websoket.EngineInputDataVO;
import cn.iocoder.yudao.module.engine.model.websoket.EngineOutputDataVO;
import cn.iocoder.yudao.module.engine.prometheus.EngineWebSocketMessageMetrics;
import cn.iocoder.yudao.module.engine.service.conn.EngineConnectionListenerManager;
import cn.iocoder.yudao.module.engine.service.core.engine.IControlEngineService;
import cn.iocoder.yudao.module.engine.service.core.queue.audio.AudioQueueFactory;
import cn.iocoder.yudao.module.engine.util.ValidateUtils;
import cn.iocoder.yudao.module.infra.util.GameMatchIdUtil;
import cn.iocoder.yudao.module.resource.api.IAudioToneApi;
import cn.iocoder.yudao.module.resource.api.ICosplayApi;
import cn.iocoder.yudao.module.resource.api.IGameMatchesApi;
import cn.iocoder.yudao.module.resource.api.model.AudioToneDTO;
import cn.iocoder.yudao.module.resource.api.model.CosplayDTO;
import cn.iocoder.yudao.module.resource.api.model.gamematches.GameMatchesDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;

import java.time.Duration;
import java.time.Instant;

@NettyWsHandlerComponent
@Slf4j
public class EngineRequestHandler {
    private static final long GET_SESSION_CTX_TIMEOUT_MS = 1000 * 30;

    @Resource
    private IControlEngineService controlEngineService;
    @Resource
    private ValidateUtils validateUtils;
    @Resource
    private EngineConnectionListenerManager engineConnectionListenerManager;
    @Resource
    private AudioQueueFactory audioQueueFactory;
    @Resource
    private EngineConfig engineConfig;
    @Resource
    private ICosplayApi cosplayApi;
    @Resource
    private IAudioToneApi audioToneApi;
    @Resource
    private EngineCacheRedisDAO engineCacheRedisDAO;
    @Resource
    private IGameMatchesApi gameMatchesApi;
    @Resource
    private EngineWebSocketMessageMetrics messageMetrics;

    @EventListener
    public void onWebSocketAuthSuccessEvent(WebSocketAuthSuccessEvent event) {
        try {
            final String gameMatchId = event.getHeaders().get(EngineConstant.HEADER_GAME_MATCH_ID);
            GameMatchIdUtil.setMatchId(gameMatchId);

            log.info("连接已创建, userId = {}, sessionId = {}, gameMatchId = {}",
                    event.getWebSocketUser().getUserId(), event.getContext().getSessionId(), gameMatchId);

            if (StrUtil.isEmpty(gameMatchId)) {
                throw ServiceExceptionUtil.exception(EnumEngineAipError.DEFAULT_ERROR, "header中未添加gameMatchId");
            }

            final WebSocketUser user = event.getWebSocketUser();
            final GameMatchesDTO gameMatches = gameMatchesApi.getGameMatches(user.getUserId(), gameMatchId);

            final Integer roleId = gameMatches.getRoleId();
            final Integer storyId = null;
            final EngineSessionContext engineSessionCtx = getOrCreateEngineSessionContext(event.getContext(), user, gameMatchId, roleId, storyId);

            engineConnectionListenerManager.onConnected(engineSessionCtx);
        } finally {
            GameMatchIdUtil.removeMatchId();
        }
    }

    @EventListener
    public void onWebSocketDisconnectEvent(WebSocketDisconnectEvent event) {
        log.info("连接已断开, userId = {}, sessionId = {}",
                event.getWebSocketUser().getUserId(), event.getContext().getSessionId());
        final WsSessionContext sessionContext = event.getContext();
        final EngineSessionContext engineSessionCtx = getEngineSessionContext(sessionContext);
        if (engineSessionCtx != null) {
            GameMatchIdUtil.setMatchId(engineSessionCtx.getGameMatchId());
            engineConnectionListenerManager.onDisconnected(engineSessionCtx);
        }
    }

    @NettyWsMapping(EngineConstant.ENGINE_INPUT_MAPPING)
    public void input(NettyWsChannelContext ctx, ClientRequest request) {
        try {
            // engineSessionContext
            final WsSessionContext sessionContext = ctx.getSession();
            final EngineSessionContext engineSessionCtx = getEngineSessionContext(sessionContext);

            GameMatchIdUtil.setMatchId(engineSessionCtx.getGameMatchId());
            final EngineInputDataVO inputData = request.readBody(EngineInputDataVO.class);
            final WebSocketUser user = ctx.getUser();
            log.info("收到引擎输入, userId = {}, inputData = {}", user.getUserId(), inputData);

            if (inputData == null || (inputData.getAudioText() == null && CollUtil.isEmpty(inputData.getSensors()))) {
                log.warn("客户端输入数据为空, inputData = {}", inputData);
                return;
            }

            validateUtils.validate(inputData);
            final EngineSynStatusCacheContext engineCache = engineCacheRedisDAO.getEngineSynStatusCache(
                    engineSessionCtx.getGameMatchId(), engineCacheRedisDAO
            );
            final EngineOutputDataVO outputData = controlEngineService.handleInputData(engineSessionCtx, engineCache, user, inputData);
            if (outputData != null && CollUtil.isNotEmpty(outputData.getSensorInstructions())) {
                // 指令
                messageMetrics.recordSuccessRequest(EnumInteractionType.COMMAND, EngineConstant.ENGINE_INPUT_MAPPING);
                messageMetrics.recordSuccessRequestDuration(EnumInteractionType.COMMAND, EngineConstant.ENGINE_INPUT_MAPPING, 1, Duration.between(inputData.getWebsocketStart(), Instant.now()));
                ctx.writeAndFlush(outputData);
            }
        } finally {
            GameMatchIdUtil.removeMatchId();
        }
    }

    private EngineSessionContext getEngineSessionContext(WsSessionContext sessionContext) {
        final EngineSessionContext engineSessionCtx;
        try {
            engineSessionCtx = sessionContext.getWithTimeout(EngineConstant.ENGINE_SESSION_KEY, GET_SESSION_CTX_TIMEOUT_MS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        if (engineSessionCtx == null) {
            log.warn("engineSessionCtx为空, sessionContext = {}", sessionContext);
            throw ServiceExceptionUtil.exception(EnumEngineAipError.DEFAULT_ERROR, "engineSessionCtx为空");
        }
        return engineSessionCtx;
    }

    private EngineSessionContext getOrCreateEngineSessionContext(WsSessionContext sessionContext, WebSocketUser user,
                                                                 String gameMatchId, Integer roleId, Integer storyId) {
        EngineSessionContext engineSessionCtx = sessionContext.getSessionValue(EngineConstant.ENGINE_SESSION_KEY);
        if (engineSessionCtx == null) {
            final CosplayDTO role = cosplayApi.getCosplay(roleId);
            final AudioToneDTO tone = audioToneApi.getById(Integer.parseInt(role.getAudioToneId()));

            engineSessionCtx = new EngineSessionContext(
                    sessionContext.getSessionId(), gameMatchId, user.getUserId(), role, tone, storyId,
                    engineConfig.getHistoryMessageMaxSize(), audioQueueFactory.getQueue(), engineConfig
            );
            sessionContext.putAndNotify(EngineConstant.ENGINE_SESSION_KEY, engineSessionCtx);
        }
        return engineSessionCtx;
    }

}
